/*
 *  SetUpdate.java        22-ott-2009
 *   
 * Copyright (c) 2009 mentalsmash.org
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 *  The use of the Apache License does not indicate that this project is
 *  affiliated with the Apache Software Foundation.
 */

package org.mentalsmash.tazio.transactions.model;

import java.io.Serializable;

import org.mentalsmash.tazio.IllegalArgumentException;
import org.mentalsmash.tazio.transactions.model.identifiers.OID;
import org.mentalsmash.tazio.transactions.util.Utility;

import com.google.common.base.Preconditions;

/**
 * A <tt>SetPropertyUpdate</tt> operation is an operation that modifies the
 * status of a TransactionalSet (e.g. the RootSet).
 * <p>
 * It could be either an object addition or object removal operation. Only
 * addition and removal of other transactional objects is supported
 * </p>
 */
public class SetUpdate extends TransactionalOperation {

    private static final long serialVersionUID = -7643569645007088771L;
    protected final static String PROPERTY_NAME = "SET_UPDATE";
    
    private final boolean removal;
    private Serializable value;

    /**
     * @param targetObject
     * @param originalVersion
     */
    public SetUpdate(final OID targetObject, final long originalVersion,
	    final boolean removal) {
	super(targetObject, originalVersion);
	this.removal = removal;
	super.setPropertyName(PROPERTY_NAME);
    }

    /**
     * True if this operation is a removal of an element from a set
     * 
     * @return <code>true</code> if this operation is a removal of an element
     *         from a set
     */
    public boolean isRemoval() {
	return this.removal;
    }

    /**
     * If this operation is a removal operation, this method returns either an
     * OID or a primitive type of the object to remove from the given
     * transactional set. Otherwise it returns the actual value (a primitive
     * type, or a TransactionalObject instance) of the object to add to a
     * collection
     * 
     * @return read the description, please
     */
    public Serializable getValue() {
	return this.value;
    }

    /**
     * If this is an insertion operation the value has to be a primitive type or
     * the instance of the TransactionalObject to add. Otherwise - if a removal
     * operation- the value has to be a primitive type or an oid.
     * 
     * @param value
     *            pleesa, read method description
     */
    public void setValue(final Serializable value) {
	Preconditions.checkNotNull(value);
	// if trying to set a primitive value its'ok in any case.
	// otherwise if this is a removal must be an OID, if it is an insertion
	// must be a TransactionalObject instance
	if (!(Utility.isPrimitiveType(value) || (this.isRemoval() ? (value instanceof OID)
		: (value instanceof TransactionalObject)))) {
	    throw new IllegalArgumentException(
		    "Only primitive types or oid (or TransactionalObjects if an insertion) are to be written");

	}

	this.value = value;
    }
    
    @Override
    public void setPropertyName(String name) {
	throw new UnsupportedOperationException("Operation not supported for SetUpdates");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.TransactionalOperation#commitOperation
     * (org.mentalsmash.tazio.transactions.RootSet)
     */
    @Override
    public void commitOperation(final RootSet rs) {
	Preconditions.checkState(getValue() != null);
	
	final TransactionalSet setToUpdate = (TransactionalSet) rs
		.getObject(getTargetObject());
	
	if(setToUpdate == null) {
	    throw new NullPointerException("Set to update " + getTargetObject() + " not found in the root set");
	}
	
	final boolean isPrimitiveValue = Utility.isPrimitiveType(getValue());
	
	Serializable toAddOrRemove;
	if(isPrimitiveValue){
	    toAddOrRemove = getValue();
	} else {
	    toAddOrRemove = rs.getObject((OID)getValue());
		if(toAddOrRemove == null) {
		    String msg = "Cannot add or remove from a set an object that is not in the root set";
		    log.error(msg);
		    throw new IllegalArgumentException(msg);
		}
	}
	
	if(this.isRemoval()){
	    if(!setToUpdate.contains(toAddOrRemove)) {
		log.warn("The value {} cannot be removed from set {}. " +
				"The set does not contain it", toAddOrRemove, getTargetObject());
	    }
	    setToUpdate.doRemove(toAddOrRemove);
	} else {
	    
	    if(setToUpdate.contains(getValue())) {
		log.warn("Te value {} will be not added to set {}. " +
				"Value already in the set", toAddOrRemove, getTargetObject());
	    }
	    setToUpdate.doAdd(toAddOrRemove);
	}
    }
}
